A Minimal Win95 GUI Program--WINDOW01.ASM
This program displays a window using very little information. It illustrates
the minimal requirements for creating a custom window.
This particular program is almost the smallest stoppable
GUI program with a custom window. To get it much smaller would require
using a predefined window class (message box, dialog, or control).
You can move the window. If you select some other
window, you can click on the window to reactivate (reselect) our program.
You can reactivate it with Alt-Tab, as well. Use Alt-F4, when it is selected,
to terminate the program.
You can also create a shortcut and use it to start
the program with the window in minimized or maximized form. Double click
the caption/title bar to change the window to "normal" size.
[Download source code.]
More programming information
Back to Win95 ASM Page
Program startup
For single-threaded programs, the initial values of the registers are almost
of no importance. But just in case you're interested: CS:EIP = start of
program; SS:ESP = start of stack; DS = ES = SS; FS = TIB, the thread information
block; GS = 0, the null selector. CS and DS map to the same linear address.
The direction flag, DF, is cleared.
[More on program
startup.]
To link with Borland's linker, the startup address is specified in the
END directive.
To link with Microsoft's linker, the startup address must be PUBLIC
and specified by the linker's /ENTRY: switch. The linker will
automatically prepend an underscore, "_".
.386
model flat
public _start
; ...
.code
_start:
; ...
end _start
The window class
Every window is described by a run-time data structure known as a window
class. (This "class" is not a C++ class.) Every window class
has a name. Associated with the window class is a callback
function known as a window procedure. The callback defines
the behavior for all windows created with this class.
Before we can create a window, its window class
must be registered. Windows preregisters a few window classes, but these
are usually used for creating controls.
There are two ways to register a custom window class:
the old way by calling RegisterClass with a WNDCLASS structure, or the
new way by calling RegisterClassEx with a WNDCLASSEX structure. The new
way allows specifying the new Win95 small icon. The old way
is required if you want to be compatible with older (pre-4.0) NT systems.
WNDCLASSEX struc ; comments show the C/C++ name
wcxSize dd ? ; cbSize, size of WNDCLASSEX
wcxStyle dd ? ; style
wcxWndProc dd ? ; lpfnWndProc
wcxClsExtra dd ? ; cbClsExtra
wcxWndExtra dd ? ; cbWndExtra
wcxInstance dd ? ; hInstance
wcxIcon dd ? ; hIcon
wcxCursor dd ? ; hCursor
wcxBkgndBrush dd ? ; hbrBackground
wcxMenuName dd ? ; lpszMenuName
wcxClassName dd ? ; lpszClassName
wcxSmallIcon dd ? ; hIconSm
WNDCLASSEX ends
Wow! that's a lot of fields. Four fields are the minimum needed to create
a window: cbSize, lpfnWndProc, hInstance, and lpszClassName. Unused fields
must be zeroed out.
The lpfnWndProc field contains the address of the
window procedure that defines how
the windows of this class will behave.
The hInstance field contains a handle
for the module that "owns" the window class. Win16 made a
distinction between an instance and a module handle, but in Win32, they
are one and the same. These module instance handles identify loaded modules
(EXE and DLL files), somewhat like file handles identifying opened files.
Like file handles, module/instance handles are unique only within a running
program "instance", or process.
The lpszClassName field contains the address of
a zero-terminated (C-style) string which names the window class. Although
Win32, in general, allows strings with either ANSI
or Unicode characters, Win95, in most cases, handles only ANSI. So
we encode the window class name in ANSI.
extrn GetModuleHandle:near,RegisterClassEx:near
.data
wc WNDCLASSEX <size WNDCLASSEX,0,WndProc,0,0, 0, 0,0,0, 0,wndclsname, 0>
wndclsname db 'window01',0
.code
push large 0 ; NULL string pointer means
call GetModuleHandle ; get HINSTANCE/HMODULE of EXE file
mov [wc.wcxInstance],eax
push offset wc
call RegisterClassEx
[See linking.]
All versions of Windows use the structure to create their own internal
window class "object", so after the window class is registered, we can
discard our "window class structure" with no ill effects.
Window creation
After the window class name is registered, we can create the window using
CreateWindowEx.
Although there are a lot of individual arguments,
we're basically providing four (and nulling out the others): 1) the class
name, 2) window location, 3) window size, and 4) an option to start with
a visible window. The last three can be nulled out provided you're willing
to perform the equivalent display functions elsewhere.
The module instance handle must be an "owner" of
the given window class, so together they uniquely identify the window class.
This ownership is established by RegisterClassEx via the hInstance field
in the WNDCLASSEX data structure.
WS_VISIBLE equ 10000000h
extrn CreateWindowEx:near
.code
push large 0 ; lpParam
push [wc.wcxInstance] ; hInstance
push large 0 ; menu hmenu
push large 0 ; parent hwnd
push large 200 ; height
push large 200 ; width
push large 100 ; y
push large 100 ; x
push large WS_VISIBLE ; Style
push large 0 ; Window text (caption)
push offset wndclsname ; Class name
push large 0 ; extended style
call CreateWindowEx
[See linking.]
The message loop
After the window is created and displayed, we can then deal with any message
sent to our program. Our minimal program repeatedly fetches messages from
the message queue with GetMessage. We do this with a message loop.
This loop is sometimes called the message pump.
If there are any messages originating from one of
the various forms of SendMessage, GetMessage will invoke the appropriate
window procedure directly. Otherwise the message is copied into a local
buffer (one of the GetMessage parameters), and control returns to the caller
of GetMessage. A zero (FALSE) value is returned if GetMessage retrieves
a WM_QUIT message.
In our program, the message loop quits if a WM_QUIT
message is retrieved. Otherwise it invokes the proper window procedure
using DispatchMessage.
MSG struc ; comments show the C/C++ names
msgHwnd dd ? ; hwnd
msgMessage dd ? ; message
msgWparam dd ? ; wParam
msgLparam dd ? ; lParam
msgTime dd ? ; time, message posting time
msgPtX dd ? ; pt.x, cursor position
msgPtY dd ? ; pt.y, cursor position
MSG ends
extrn GetMessage:near,DispatchMessage:near
.data
msgbuf MSG <>
.code
msg_loop:
push large 0 ; uMsgFilterMax
push large 0 ; uMsgFilterMin
push large 0 ; hWnd (filter), 0 = all windows
push offset msgbuf ; lpMsg
call GetMessage ; returns FALSE if WM_QUIT
or eax,eax
jz end_loop
push offset msgbuf
call DispatchMessage
jmp msg_loop
end_loop:
[See linking.]
Terminating the program
We exit the program with ExitProcess.
extrn ExitProcess:near
.code
push large 0 ; (error) return code
call ExitProcess
[See linking.]
The window procedure
Except for some initialization and some cleanup, a GUI program is expected
to do everything within the various window procedures. The window
procedure may be called by Windows itself (e.g., when a window is first
created), or it may be invoked via DispatchMessage.
When a window is closed (e.g., with Alt-F4), the
default action, handled by DefWindowProc, is to destroy it. The window
is removed from the screen, and a WM_DESTROY message is sent to the window
procedure. If we want to terminate our program by closing some specific
window, then that window must respond to a message associated with closing
a window. The recommended message is WM_DESTROY. Our window procedure does
this for the one and only window, calling PostQuitMessage as a response.
After the window procedure is exited, a WM_QUIT message will be picked
up by GetMessage in our message loop.
All other messages get processed by DefWindowProc.
One of the advantages of going from 16-bit code to 32-bit code is the
extra addressing modes available. All eight primary 32-bit registers can
be used in complex addressing modes.
WndProc takes advantage of this by using ESP to
access its arguments. We use the decomposed form [ESP+4+4] to show that
part of the offset is for skipping the stacked EIP and the other part is
the offset to the second argument (the message ID).
extrn DefWindowProc:near,PostQuitMessage:near
WM_DESTROY equ 2
.code
WndProc:
cmp dword ptr [esp+4+4],WM_DESTROY
jne DefWindowProc
start_destroy:
push large 0
call PostQuitMessage
xor eax,eax
ret 16
[See linking.]
Strings, ANSI and Unicode
Character data can be in either ANSI (8-bit) or Unicode (16-bit). Consequently,
most (not all!) Win32 functions which handle strings, directly or indirectly,
have two versions--one for ANSI and one for Unicode. The functions are
distinguished by appending A or W (wide) to the function name.
Unicode is more efficient on NT, but Win95 has only
a limited Unicode capability. So, except for a few cases, Win95 programs
use ANSI characters and strings.
Most Windows strings follow the C standard of terminating
with a zero-valued (NUL) character.
Linking
If you're a DOS programmer, you might be wondering what INT's are being
used to call API routines such as ExitProcess. The answer is none.
The API functions being called are located in DLL (dynamic
link library) files. (How the program switches between the "application"
and OS "kernel" modes requires knowledge of how the Intel 386 and later
chips implement protected mode and memory paging.)
The linker doesn't add the DLL code into the EXE
file. Instead, it produces references to DLL entry points in the EXE files.
This is done via associated import libraries. You must tell
the linker what import libraries are needed. Unfortunately, there isn't
a standard import library format. Below are examples for using the Borland
and Microsoft libraries..
An important feature needed in your assembler is
preservation of letter case, because Win32 API names are case-sensitive.
On many assemblers this is an option which is normally disabled.
Borland uses one library, import32.lib, for the core set of Win32 functions.
The import link names are the same as the entry point names in the DLL.
CreateWindowEx equ <CreateWindowExA>
DefWindowProc equ <DefWindowProcA>
DispatchMessage equ <DispatchMessageA>
GetMessage equ <GetMessageA>
GetModuleHandle equ <GetModuleHandleA>
RegisterClassEx equ <RegisterClassExA>
Microsoft uses several libraries, one per DLL. The link names for Win32
functions are "decorated" names. The rule is simple: prepend an underscore
(_) and append an at-sign (@) and the number of argument
bytes in decimal. So the ANSI version of GetMessage, which has four DWORD
arguments, is linked as _GetMessageA@16 (_ + GetMessage
+ A + @ + 16 [= 4*4]).
This decoration scheme is primarily for the convenience
of Microsoft compilers. The link name needn't have this relationship to
the entry point name, but this scheme is used by the Microsoft Win32 libraries.
CreateWindowEx equ <_CreateWindowExA@48>
DefWindowProc equ <_DefWindowProcA@16>
DispatchMessage equ <_DispatchMessageA@4>
ExitProcess equ <_ExitProcess@4>
GetMessage equ <_GetMessageA@16>
GetModuleHandle equ <_GetModuleHandleA@4>
PostQuitMessage equ <_PostQuitMessage@4>
RegisterClassEx equ <_RegisterClassExA@4>